﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace CivLib.CivHyd
{

  /// <summary>
  /// Interface that defines an object that can return flow given
  /// a specified head
  /// </summary>
  public interface IOutlet
  {
    /// <summary>
    /// Returns the flow at the specified head
    /// </summary>
    /// <param name="a_depth">head</param>
    /// <returns>Q [cfs] [cms]</returns>
    double FlowAtDepth(double a_depth);
  }

  /// <summary>
  /// Encapsulates outlet with an height in the datum
  /// </summary>
  public class OutletObj : IOutlet, IComparable
  {
    IOutlet m_outletEqn;
    double m_height;

    /// <summary>
    /// Get and set IOutlet that governs this Outlet
    /// </summary>
    public IOutlet OutletEqn
    {
      get { return m_outletEqn; }
      set { m_outletEqn = value; }
    }

    /// <summary>
    /// Get and set the height of the outlet from the datum
    /// </summary>
    public double Height
    {
      get { return m_height; }
      set { m_height = value; }
    }

    /// <summary>
    /// Constructor for outlet
    /// </summary>
    /// <param name="a_outlet">Outlet type to be encapsulated</param>
    /// <param name="a_height">height from datum</param>
    public OutletObj(IOutlet a_outlet, double a_height)
    {
      m_outletEqn = a_outlet;
      m_height = a_height;
    }

    /// <summary>
    /// This behaves slighly different than other IOutlet types
    /// it will subtract off the height of the orfice first and then
    /// get use that depth.  If the depth - height is less than zero then 
    /// it will return 0
    /// </summary>
    /// <param name="a_depth">depth from the datum</param>
    /// <returns>flow with given depth from datum</returns>
    public double FlowAtDepth(double a_depth)
    {
      double headOnOutlet = a_depth - m_height;
      return headOnOutlet < 0 ? 0 : m_outletEqn.FlowAtDepth(headOnOutlet);
    }

    /// <summary>
    /// To implament IComparable
    /// </summary>
    /// <param name="obj">object to compare to</param>
    /// <returns>the combparison of the double height to the object</returns>
    public int CompareTo(object obj)
    {
      return m_height.CompareTo(obj);
    }
  }

  /// <summary>
  /// List of outlet Objects with associated heights
  /// </summary>
  public class OutletList : IOutlet
  {
    List<OutletObj> m_outlets;
    //SortedList<double, IOutlet> m_outlets = new SortedList<double, IOutlet>(4);

    /// <summary>
    /// Allows access to the underlying sorted list of IOutlets with depth as
    /// their keys
    /// </summary>
    public List<OutletObj> Outlets
    {
      get { return m_outlets; }
      set { m_outlets = value; }
    }

    /// <summary>
    /// Indexer for accessing the contained outlets
    /// </summary>
    /// <param name="i">0 based index of outlet</param>
    /// <returns>the Outlet Obj</returns>
    public OutletObj this[int i]
    {
      get { return m_outlets[i]; }
      set { m_outlets[i] = value; }
    }

    /// <summary>
    /// The number of outlets in this list
    /// </summary>
    public int Count
    {
      get { return m_outlets.Count; }
    }

    /// <summary>
    /// Constructor that initializes with an empty list
    /// </summary>
    public OutletList()
    {
      m_outlets = new List<OutletObj>(4);
    }

    /// <summary>
    /// Constructor for the outlet list with the number of outlets.  Adds a
    /// default outlet with a size of 1 in 1ft height incraments.
    /// </summary>
    /// <param name="a_numberOfOutlets">number of outlets to add</param>
    public OutletList(int a_numberOfOutlets)
    {
      m_outlets = new List<OutletObj>(a_numberOfOutlets);
      for (int i = 0; i < a_numberOfOutlets; i++)
      {
        AddOutlet(new CivNum.Orifice(), i);
      }
    }

    /// <summary>
    /// Copying the outlets from the list passed in
    /// </summary>
    /// <param name="a_copyMe">OuletList to copy from</param>
    public OutletList(OutletList a_copyMe)
    {
      for (int i = 0; i < a_copyMe.Count; i++)
      {
        AddOutlet(a_copyMe[i]);
      }
    }

    /// <summary>
    /// Adds an oulet to the list  If the specific depth is taken in the list
    /// then it deincraments a_depth by double.Epsilon till the key is not taken.
    /// </summary>
    /// <param name="a_outlet">IOutlet to be added</param>
    /// <param name="a_height">depth to add outlet at</param>
    public void AddOutlet(IOutlet a_outlet, double a_height)
    {
      OutletObj addMe = new OutletObj(a_outlet, a_height);
      m_outlets.Add(addMe);
    }

    /// <summary>
    /// Add a outlet object to the list
    /// </summary>
    /// <param name="a_outlet">outlet object to be added</param>
    public void AddOutlet(OutletObj a_outlet)
    {
      m_outlets.Add(a_outlet);
    }

    /// <summary>
    /// Clears all outlets from the list
    /// </summary>
    public void Clear()
    {
      m_outlets.Clear();
    }

    /// <summary>
    /// Get flow at specified depth.  Iterates through all outlets in the
    /// list and returns the sum of all the flows.
    /// </summary>
    /// <param name="a_depth">depth to evaluate at</param>
    /// <returns>the sum of all outlet flow at spefified depth</returns>
    public double FlowAtDepth(double a_depth)
    {
      double flowSum = 0;

      foreach (OutletObj Outlet in m_outlets)
      {
        flowSum += Outlet.FlowAtDepth(a_depth);
      }

      return flowSum;
    }
  }
}
